在|D3|從 vue 原始碼看 new Vue 做了什麼中提到,當 new Vue 後這個 Vue 實例就有了初始化的屬性或方法。
接著要使用 Vue 實例的私有方法 _render
function ,把實例渲染成一個虛擬 DOM。
了解虛擬 DOM 之後再來看 _render
function。
回顧一下,瀏覽器解析 HTML 構成 DOM tree,DOM tree 結構由各個節點( node )組成。
圖片來源:google-developers
以 div 元素為例,用 console.dir
把這個 DOM 所有 properties 印出來(只截圖部分而已)。
可以看到 DOM 非常龐大,每當用原生 JS 操作 DOM 時,瀏覽器會再次構成 DOM tree 重新渲染。
Virtual DOM 不是真正的 DOM,概念是當 data 狀態發生變化,Virtual DOM 會進行 diff 運算,只重新渲染需要被取代的 DOM,非常輕量。
在 src/core/vdom/vnode.js
中
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
// strictly internal
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
asyncFactory: Function | void; // async component factory function
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
fnContext: Component | void; // real context vm for functional nodes
fnOptions: ?ComponentOptions; // for SSR caching
devtoolsMeta: ?Object; // used to store functional render context for devtools
fnScopeId: ?string; // functional scope id support
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
this.tag = tag
this.data = data
this.children = children
this.text = text
this.elm = elm
this.ns = undefined
this.context = context
this.fnContext = undefined
this.fnOptions = undefined
this.fnScopeId = undefined
this.key = data && data.key
this.componentOptions = componentOptions
this.componentInstance = undefined
this.parent = undefined
this.raw = false
this.isStatic = false
this.isRootInsert = true
this.isComment = false
this.isCloned = false
this.isOnce = false
this.asyncFactory = asyncFactory
this.asyncMeta = undefined
this.isAsyncPlaceholder = false
}
// DEPRECATED: alias for componentInstance for backwards compat.
/* istanbul ignore next */
get child (): Component | void {
return this.componentInstance
}
}
Virtual DOM 用 VNode
Class 描述基礎的 VNode
elm
: 當前虛擬節點相對應的真實 DOM 節點data
: 包含 class、id等 HTML 屬性VNodeData 的定義可從下面 2 中知道
types/vnode.d.ts
中export interface VNodeData {
key?: string | number;
slot?: string;
scopedSlots?: { [key: string]: ScopedSlot | undefined };
ref?: string;
refInFor?: boolean;
tag?: string;
staticClass?: string;
class?: any;
staticStyle?: { [key: string]: any };
style?: string | object[] | object;
props?: { [key: string]: any };
attrs?: { [key: string]: any };
domProps?: { [key: string]: any };
hook?: { [key: string]: Function };
on?: { [key: string]: Function | Function[] };
nativeOn?: { [key: string]: Function | Function[] };
transition?: object;
show?: boolean;
inlineTemplate?: {
render: Function;
staticRenderFns: Function[];
};
directives?: VNodeDirective[];
keepAlive?: boolean;
}
舉個例子
<div id="app">
<p class="text">{{ message }}</p>
<ul>
<li v-for="user of users" class="item">{{ user }}</li>
</ul>
</div>
<scrip>
new Vue({
el: '#app',
data: {
message: 'hello',
users: ['Tom', 'Tony', 'Tim']
}
})
</scrip>
node tree
{
'tag': 'div'
'data': {
'attrs': { 'id': 'app' }
},
'children': [
{
'tag': 'p',
'data': {
'staticClass': 'text'
}
'text': 'hello'
},
{
'tag': 'ul',
'children': [
{
'tag': 'li',
'data': {
'staticClass': 'item'
}
'text': 'Tom'
},
{
'tag': 'li',
'data': {
'staticClass': 'item'
}
'text': 'Tony'
},
{
'tag': 'li',
'data': {
'staticClass': 'item'
}
'text': 'Tim'
}
]
}
]
}